#pragma once
#include "../Utility/Tools.hpp"
#include "../Utility/Timer.hpp"
#include "../Utility/Log.hpp"
#include "Math.hpp"

//Iteration Exit Condition
//Condition can be combination of abs(residue), iteration times more, abs(residue difference), or running time
//If any condition satisfied, IsSatisfied will return true to indicate stop iteration

namespace zzz{
template<typename T>
class IterExitCond
{
public:
  //if true, exit
  bool IsSatisfied(T residue) {
    bool ret=false;

    ZLOG(ZDEBUG)<<"Iteration:"<<iterCount_;
    iterCount_++;
    if (CheckBit(exitCond_,ITERCOUNT))
      if (iterCount_>=iterCountCond_) ret=true;

    ZLOG(ZDEBUG)<<" Residue:"<<residue;
    if (CheckBit(exitCond_,RESIDUE)) {
      if (Abs(residue)<=residueCond_) residueCount_++;
      else residueCount_=0;
      ZLOG(ZDEBUG)<<" ResidueCount:"<<residueCount_;
      if (residueCount_>=residueCountCond_) ret=true;
    }

    if (CheckBit(exitCond_,RESIDUEDIFF)) {
      if (iterCount_>1) {
        T diff=Abs(lastResidue_-residue);
        ZLOG(ZDEBUG)<<" Diff:"<<diff;
        if (diff<=residueDiffCond_) residueDiffCount_++;
        else residueDiffCount_=0;
        ZLOG(ZDEBUG)<<" DiffCount:"<<residueDiffCount_;
        if (residueDiffCount_>=residueDiffCountCond_) ret=true;
      }
      lastResidue_=residue;
    }

    if (CheckBit(exitCond_,TIME)) {
      double time=timer_.Elapsed();
      if (time>=timeCond_) ret=true;
      ZLOG(ZDEBUG)<<" RunningTime:"<<time;
    }

    if (CheckBit(exitCond_, REPEAT)) {
      history_.push_front(residue);
      if (history_.size()>repeatLengthCond_*repeatTimesCond_)
        history_.pop_back();
      for (zuint i=2; i<=repeatLengthCond_; i++) {
        if (history_.size()>=i*repeatTimesCond_) {
          bool all_equal=true;
          for (zuint k=1; k<i; k++) {
            if (history_[k]!=history_.front()) {
              all_equal=false;
              break;
            }
          }
          if (all_equal)
            continue;
          bool foundRepeat=true;
          for (zuint j=1; j<repeatTimesCond_; j++) {
            if (!equal(history_.begin(), history_.begin()+i, history_.begin()+j*i)) {
              foundRepeat=false;
              break;
            }
          }
          if (foundRepeat) {
            ret=true;
            ZLOGD<<" Repeat("<<i<<", "<<repeatTimesCond_<<")";
            break;
          }
        }
      }
    }

    ZLOG(ZDEBUG)<<endl;
    //if (ret) Reset();  //if set maybe more convinient but difficult to debug
    return ret;
  }

  bool NeedLoop(T residue) {
    return !IsSatisfied(residue);
  }

  void Reset()
  {
    iterCount_=0;
    residueCount_=0;
    lastResidue_=0;
    residueDiffCount_=0;
    timer_.Restart();
  }

  IterExitCond():exitCond_(0){Reset();}
//set conditions
  void ClearAllCond()
  {
    exitCond_=0;
  }

  void SetCondResidue(T residue, zuint resicount=1)
  {
    residueCond_=residue;
    residueCountCond_=resicount;
    SetBit(exitCond_,RESIDUE);
  }
  void ClrCondResidue()
  {
    ClearBit(exitCond_,RESIDUE);
  }

  void SetCondIterCount(int i)
  {
    iterCountCond_=i;
    SetBit(exitCond_,ITERCOUNT);
  }
  void ClrCondIterCount()
  {
    ClearBit(exitCond_,ITERCOUNT);
  }

  void SetCondResidueDiff(T residiff, zuint residiffcount=1)
  {
    residueDiffCond_=residiff;
    residueDiffCountCond_=residiffcount;
    SetBit(exitCond_,RESIDUEDIFF);
  }
  void ClrCondResidueDiff()
  {
    ClearBit(exitCond_,RESIDUEDIFF);
  }

  void SetCondRunningTime(double seconds)
  {
    timeCond_=seconds;
    SetBit(exitCond_,TIME);
  }
  void ClrCondRunnintTIme()
  {
    ClearBit(exitCond_,TIME);
  }

  void SetCondRepeat(zuint repeat_length=10, zuint repeat_times=3)
  {
    repeatLengthCond_=repeat_length;
    repeatTimesCond_=repeat_times;
    history_.clear();
    SetBit(exitCond_,REPEAT);
  }
  void ClrCondRepeat()
  {
    ClearBit(exitCond_,REPEAT);
  }

private:
  //condition
  enum{RESIDUE=1,ITERCOUNT=2,RESIDUEDIFF=4,TIME=8,REPEAT=16};
  int exitCond_;

  // iter count
  zuint iterCountCond_;
  // residue
  T residueCond_;
  zuint residueCountCond_;
  // residue diff
  T residueDiffCond_;
  zuint residueDiffCountCond_;
  // running time
  double timeCond_;
  // repeat
  zuint repeatLengthCond_;
  zuint repeatTimesCond_;
  deque<T> history_;

  //real data
  zuint iterCount_;
  zuint residueCount_;
  T lastResidue_;
  zuint residueDiffCount_;
  Timer timer_;
};
}